//
// Created by benxb on 2021/11/15.
//

#include <fstream>
#include <utility>
#include <dirent.h>
#include <sys/stat.h>
#include "catelog/table.h"
#include "common/exception.h"
#include "common/logger.h"

namespace benxdb {

bool Table::SaveMetaData() {

    // check if dir exist;
    std::string data_dir = GlobalConfig::DatasetDir(table_name_);
    if (!opendir(data_dir.c_str())) {
        mkdir(data_dir.c_str(), 0775);
    }

    std::string meta_file = GlobalConfig::MetaFilePath(table_name_);
    std::fstream db_io_(meta_file, std::ios::binary | std::ios::in | std::ios::out);
    // directory or file does not exist
    if (!db_io_.is_open()) {
        db_io_.clear();
        // create a new file
        db_io_.open(meta_file, std::ios::binary | std::ios::trunc | std::ios::out);
        db_io_.close();
        // reopen with original mode
        db_io_.open(meta_file, std::ios::binary | std::ios::in | std::ios::out);
        if (!db_io_.is_open()) {
            throw Exception("can't open meta file " + meta_file);
        }
    }

    // write lsn
    db_io_.write(reinterpret_cast<const char *>(&lsn_), sizeof(lsn_));
    db_io_.write(reinterpret_cast<const char *>(&flush_lsn_), sizeof(flush_lsn_));

    // write columns
    uint32_t column_size = columns_.size();
    db_io_.write(reinterpret_cast<const char *>(&column_size), sizeof(column_size));
    for (const Column& col : columns_) {
        std::string col_data = col.Serialize();
        uint32_t col_data_size = col_data.size();
        db_io_.write(reinterpret_cast<const char *>(&col_data_size), sizeof(col_data_size));
        db_io_.write(col_data.data(), col_data.size());
    }

    //write custom data_
    uint32_t custom_data_size = custom_data_.size();
    db_io_.write(reinterpret_cast<const char *>(&custom_data_size), sizeof(custom_data_size));
    for (std::pair<std::string, std::string> custom : custom_data_) {
        uint32_t key_size = custom.first.size();
        uint32_t val_size = custom.second.size();
        db_io_.write(reinterpret_cast<const char *>(&key_size), sizeof(key_size));
        db_io_.write(custom.first.data(), key_size);
        db_io_.write(reinterpret_cast<const char *>(&val_size), sizeof(val_size));
        db_io_.write(custom.second.data(), val_size);
    }

    // check for I/O error
    if (db_io_.bad()) {
        LOG_DEBUG("I/O error while writing");
        return false;
    }
    // needs to flush to keep disk file in sync
    db_io_.flush();
    db_io_.close();
    return true;
}

Table::Table(const std::string& table_name) : table_name_(table_name) {
    std::string meta_file = GlobalConfig::MetaFilePath(table_name);
    std::fstream db_io_(meta_file, std::ios::binary | std::ios::in | std::ios::out);
    // directory or file does not exist
    if (!db_io_.is_open()) {
        db_io_.clear();
        // create a new file
        db_io_.open(meta_file, std::ios::binary | std::ios::trunc | std::ios::out);
        db_io_.close();
        // reopen with original mode
        db_io_.open(meta_file, std::ios::binary | std::ios::in | std::ios::out);
        if (!db_io_.is_open()) {
            throw Exception("can't open meta file " + meta_file);
        }
        return;
    }

    // file exist, read meta data_
    // read lsn
    db_io_.seekp(0);
    db_io_.read(reinterpret_cast<char *>(&lsn_), sizeof(lsn_));
    db_io_.read(reinterpret_cast<char *>(&flush_lsn_), sizeof(flush_lsn_));

    // read columns
    uint32_t column_size = 0;
    db_io_.read(reinterpret_cast<char *>(&column_size), sizeof(column_size));
    for (int i = 0; i < column_size; i++) {
        std::string col_data;
        uint32_t col_data_size;
        db_io_.read(reinterpret_cast<char *>(&col_data_size), sizeof(col_data_size));
        col_data.resize(col_data_size);
        db_io_.read(const_cast<char *>(col_data.data()), col_data_size);
        AddColumn(Column(col_data));
    }

    //read custom data_
    uint32_t custom_data_size = 0;
    db_io_.read(reinterpret_cast<char *>(&custom_data_size), sizeof(custom_data_size));
    for (int i = 0; i < custom_data_size; i++) {
        uint32_t key_size;
        uint32_t val_size;
        db_io_.read(reinterpret_cast<char *>(&key_size), sizeof(key_size));
        std::string key(key_size, 0);
        db_io_.read(const_cast<char *>(key.data()), key_size);
        db_io_.read(reinterpret_cast<char *>(&val_size), sizeof(val_size));
        std::string value(val_size, 0);
        db_io_.read(const_cast<char *>(value.data()), val_size);
        custom_data_.insert({key, value});
    }
    db_io_.close();
    LOG_DEBUG("table lsn : %d", GetLSN());
}

Table::~Table() = default;

bool Table::GetCustomData(const std::string &key, std::string &val) {
    if (custom_data_.find(key) == custom_data_.end()) {
        return false;
    }
    val = custom_data_[key];
    return true;
}

void Table::SetCustomData(const std::string &key, const std::string &value) {
    custom_data_[key] = value;
}

int Table::GetColumnIndex(const std::string &column_name)  {
    for (const Column& col : columns_) {
        if (col.GetName() == column_name) {
            return col.GetOffset();
        }
    }
    return -1;
}

Column Table::GetColumn(uint32_t column_index) {
    if (column_index < columns_.size()) {
        return columns_[column_index];
    }
    return Column("", INVALID);
}

void Table::SaveLSN() {
    std::string meta_file = GlobalConfig::MetaFilePath(table_name_);
    std::fstream db_io_(meta_file, std::ios::binary | std::ios::in | std::ios::out);
    db_io_.seekp(LSN_OFFSET);
    db_io_.write(reinterpret_cast<const char *>(&lsn_), sizeof(lsn_));
    db_io_.write(reinterpret_cast<const char *>(&flush_lsn_), sizeof(flush_lsn_));
    db_io_.flush();
    db_io_.close();
}


}